package com.boshanam.model.dao.jpa.google.daoimpl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.support.JpaDaoSupport;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;

import com.boshanam.exceptions.domain.user.EmailIdAlreadyRegisteredException;
import com.boshanam.exceptions.domain.user.InvalidUserException;
import com.boshanam.exceptions.domain.user.UserAlreadyExistException;
import com.boshanam.exceptions.domain.user.UserNameAlreadyExistsException;
import com.boshanam.exceptions.domain.user.UserNotExistException;
import com.boshanam.exceptions.runtime.RuntimeApplicationException;
import com.boshanam.model.dao.IUserDao;
import com.boshanam.model.entities.persistant.jpa.user.User;
import com.boshanam.model.vos.domain.user.UserVO;
import com.boshanam.model.vos.domain.user.UserVOImmutable;
import com.boshanam.util.user.UserUtils;

/**
 * @author Sivakumar Y
 * 
 *         UserDaoGoogleDSImpl.java
 * 
 *         Dec 17, 2009 6:57:42 PM
 * 
 *         shiva.forums@gmail.com
 */
public class UserDaoGoogleDSImpl extends JpaDaoSupport implements IUserDao {

	Log log = LogFactory.getLog(UserDaoGoogleDSImpl.class);

	public UserDaoGoogleDSImpl() {
	}

	@Override
	public void save(User user) throws UserAlreadyExistException, DataAccessException, UserNameAlreadyExistsException,
			EmailIdAlreadyRegisteredException {

		EntityManager em = this.getJpaTemplate().getEntityManagerFactory().createEntityManager();
		if (em == null) {
			log.fatal("EntityManager not got from Spring, Problem in Persistance system...");
			/*
			 * em = EMF.getEmfInstance().createEntityManager();
			 * 
			 * ApplicationContext context =
			 * WebApplicationContextUtils.getRequiredWebApplicationContext
			 * (null); em =
			 * ((EntityManagerFactory)context.getBean("googleDsEMF")
			 * ).createEntityManager();
			 */
			if (em == null) {
				log.fatal("EntityManager not got from EMF, Problem in Persistance system...");
				throw new TransientDataAccessResourceException(
						"EntityManager not got From Spring and from local EMF, Problem in Persistance system...");
			}
		}

		EntityTransaction tx = em.getTransaction();

		if (tx == null) {
			log.fatal("EntityTransaction not got , Problem in Persistance system...");
			throw new TransientDataAccessResourceException(
					"EntityTransaction not got , Problem in Persistance system...");
		}

		tx.begin();
		try {
			log.fatal("USER_DAO-The Locale associated with user: " + user.getUserSettings().getLocale());
			em.persist(user);
			if (log.isTraceEnabled()) {
				log.trace("[Save User]- Querying for User Email ID...");
			}

			Query q = em.createQuery("SELECT u FROM User u Where u.email = :emailId");// OR
																						// u.username
																						// =
																						// :userName");
			q.setParameter("emailId", user.getEmail());
			// q.setParameter("userName", user.getUsername());

			List<?> users = q.getResultList();
			int EXISTING_USER_NUMBER = users.size();

			// There is a possibility that, one user exists with given EmailID
			// and
			// one another user with Given USERNAME. Its a valid case and not a
			// problem, but if more than two people exists, then its FATAL ERROR
			// In
			// our system that, username or email is used by more than one
			// person.
			// So raise an Exception in that case and log users.

			// TODO Create an Alerting System and create a TICKET with the
			// problematic User entities so they can be handled and resolved by
			// administrators.
			if (EXISTING_USER_NUMBER > 2) {
				StringBuilder sb = new StringBuilder();
				int i = 1;
				for (Object o : users) {
					User u = (User) o;
					sb.append("USER-" + i + ": ");
					sb.append(u + "\n");
				}

				log.fatal("Multiple(more than two) Users exists with given EMAILID, USERNAME. TRACE: \n"
						+ "-------------------------------------------------------------------------------" + "\n"
						+ sb.toString() + "\n"
						+ "-------------------------------------------------------------------------------\n");
			} else if (EXISTING_USER_NUMBER <= 2 && EXISTING_USER_NUMBER >= 1) {
				if (EXISTING_USER_NUMBER == 1) {
					User u = (User) users.get(0);

					// username and emailid both already taken by single user
					if (u.getUsername().equalsIgnoreCase(user.getUsername())
							&& u.getEmail().equalsIgnoreCase(user.getEmail())) {

						StringBuilder msg = new StringBuilder();
						msg.append(
								"Trying to persist already registered USER: USER ALREADY EXISTS... \n\t Existing USER: ")
								.append(u).append("\n\t NEW USER: ").append(user);

						log.error(msg.toString());
						if (u.getUsername().equalsIgnoreCase(u.getEmail())) {
							throw new EmailIdAlreadyRegisteredException(
									"This Email is Already Registered please try with other one.");
						}
						throw new UserAlreadyExistException(msg.toString());
					} else if (u.getUsername().equalsIgnoreCase(user.getUsername())) {
						String msg = "Trying to persist user with already registered USERNAME: USERNAME ALREADY EXISTS... \n\t USER: "
								+ user;
						log.error(msg);
						throw new UserNameAlreadyExistsException(msg);
					} else {
						String msg = "Trying to persist user with already registered EMAIL: EMAIL ALREADY Registered... \n\t USER: "
								+ user;
						log.error(msg);
						throw new EmailIdAlreadyRegisteredException(msg);
					}
				} else {
					String msg = "Trying to persist user with already registered EMAIL or USERNAME: \n\t New USER Requested: "
							+ user
							+ "\n\t Existing Users: \n\t User 1: "
							+ ((User) users.get(0))
							+ "\n\t User 2: "
							+ ((User) users.get(1));
					log.error(msg);
					throw new UserAlreadyExistException(msg);
				}

			} else { // NO Existing Users with given EMAIL and USERNAME
				log.debug("Creating New User: Committing Transaction...");
				tx.commit();
				log.debug("Created New User: Transaction Committed...");
			}

		} finally {
			if (tx.isActive()) {
				tx.rollback();
			}

			try {
				em.close();
			} catch (Exception e) {
				log.trace("Problem closing EntityManager: ", e);
			}

		}

		// if (log.isTraceEnabled()) {
		// log.trace("[Save User]- Querying for User Email ID..."); }
		//
		// Query q = em.createQuery(
		// "SELECT u FROM User u Where u.email = :emailId || u.username = :userName"
		// ); q.setParameter("emailId", user.getEmail());
		//
		// try { User u = (User) q.getSingleResult();
		// log.debug("User already exist with this Email ID" + u); throw new
		// EmailAlreadyRegisteredException(
		// "User already registered with this Email id: " + user.getEmail()); }
		// catch (NonUniqueResultException e) { log.fatal(
		// "Multiple Users With Single Email ID: " + user.getEmail(), e); throw
		// new IllegalStateException( "Multiple Users With Single Email ID: " +
		// user.getEmail(), e); } catch (NoResultException e) { // In Every bad
		// case we are throwing Exceptions... // If no exception everything
		// seems good proceed to persist by commit... }
		//
		// q =
		// em.createQuery("SELECT u FROM User u Where u.username = :userName");
		// q.setParameter("userName", user.getUsername());
		//
		// try { User u = (User) q.getSingleResult();
		// log.debug("User already exist with this USER ID" + u); throw new
		// EmailAlreadyRegisteredException(
		// "User already registered with this USER id: " + user.getUsername());
		// } catch (NonUniqueResultException e) {
		// log.fatal("Multiple Users With Single USER ID: " +
		// user.getUsername(), e); throw new IllegalStateException(
		// "Multiple Users With Single USER ID: " + user.getUsername(), e); }
		// catch (NoResultException e) { // In Every bad case we are throwing
		// Exceptions... // If no exception everything seems good proceed to
		// persist by // commit... }
		//

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.ibigbang.dao.UserDao#delete(net.ibigbang.model.persistant.google.
	 * users.User)
	 */
	@Override
	public void delete(User user) throws UserNotExistException, DataAccessException {

		EntityManager em = this.getJpaTemplate().getEntityManager();
		EntityTransaction tx = em.getTransaction();
		tx.begin();

		try {
			Object o = em.find(User.class, user.getKey());
			em.remove(o);

			tx.commit();
		} catch (Exception e) {
			if (log.isDebugEnabled()) {
				log.debug("Problem Deleting User: " + user, e);
			}
			throw new RuntimeApplicationException("Unable to Delete the User from Datastore.", e);
		} finally {
			if (tx.isActive()) {
				tx.rollback();
			}
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.ibigbang.dao.UserDao#getAllUsers(int)
	 */
	@Override
	public List<UserVOImmutable> getAllUsers(int max) {
		// TODO Auto-generated method stub
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.ibigbang.dao.UserDao#getAllUsers()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<UserVOImmutable> getAllUsers() {
		return this.getJpaTemplate().executeFind(new JpaCallback() {
			@Override
			public List<UserVOImmutable> doInJpa(EntityManager em) throws PersistenceException {
				Query q = em.createQuery("SELECT FROM User");
				List<User> result = (List<User>) q.getResultList();
				List<UserVOImmutable> voList = new ArrayList<UserVOImmutable>();
				if (result != null && result.size() > 0) {
					for (User u : result) {
						voList.add(UserUtils.createImmutableVoFromEntity(u));
					}
					return voList;
				} else {
					return Collections.emptyList();
				}
			}

		});
	}

	/**
	 * This method implementation presently delegate to
	 * loadUserByUsername(userName) method as it accepts both Email or userName
	 * as userName String.
	 * 
	 * @see com.boshanam.model.dao.IUserDao.dao.UserDao#getUserByEmail(java.lang.String)
	 */
	@Override
	public UserVOImmutable getUserByEmail(final String email) throws UsernameNotFoundException {
		return (UserVOImmutable) this.getJpaTemplate().execute(new JpaCallback() {

			@Override
			public Object doInJpa(EntityManager em) throws PersistenceException {
				Query q = em.createQuery("SELECT u FROM User u Where u.email = :emailId");
				q.setParameter("emailId", email);

				Object o;
				try {
					o = q.getSingleResult();
					User u = (User) o;
					u.getUserSettings();
					return UserUtils.createImmutableVoFromEntity(u);
				} catch (NoResultException e) {
					throw new UsernameNotFoundException("User Not Found with Given Email Id", e);
				} catch (NonUniqueResultException e) {
					log.fatal("Multiple Users Found with Email ID", e);
					throw new RuntimeApplicationException("Multiple Users Found with Email ID", e);
				}
			}

		});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.ibigbang.dao.UserDao#getUserCount()
	 */
	@Override
	public long getUserCount() {
		// TODO Auto-generated method stub
		return 0;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.ibigbang.dao.UserDao#getUserForId(java.lang.String)
	 */
	@Override
	public UserVOImmutable getUserForId(String id) throws UserNotExistException {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * Spring Security Compatible method to load user by user name.
	 */
	public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException, DataAccessException {

		return (UserDetails) this.getJpaTemplate().execute(new JpaCallback() {

			@Override
			public Object doInJpa(EntityManager em) throws PersistenceException {
				Query q = em.createQuery("SELECT u FROM User u Where u.username = :userName");
				q.setParameter("userName", username);

				Object o;
				try {
					o = q.getSingleResult();
					User u = (User) o;
					u.getUserSettings();
					return UserUtils.createImmutableVoFromEntity(u);
				} catch (NoResultException e) {
					throw new UsernameNotFoundException("User Not Found with Given User Name", e);
				} catch (NonUniqueResultException e) {
					log.fatal("Multiple Users Found with User name", e);
					throw new RuntimeApplicationException("Multiple Users Found with User name", e);
				}
			}

		});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.ibigbang.dao.UserDao#updateUser(net.ibigbang.model.vos.users.UserVO)
	 */
	@Override
	public UserVO updateUser(UserVO user) throws InvalidUserException, DataAccessException {

		User u = UserUtils.createEntityFromVO(user);

		return UserUtils.createVoFromEntity(getJpaTemplate().merge(u));

	}
}
